(function (fruit, util) {

    fruit.ui.define("fruit.graphic.Topology", fruit.graphic.core, {
        events: ['ClickStage', 'SelectNode', 'UnSelectNode', 'SelectLink', 'OverNode', 'OutNode', 'Zooming', 'DragStart', 'Draging', 'DragEnd', 'Updating', 'OverInterface' , 'OutInterface', 'OverLink', 'OutLink', 'DragNodeStart', 'DragNode', 'DragNodeEnd'],
        view: {
            props: {
                'class': 'f-graphic f-topology f-topology-highContrast-{#highContrast}',
                style: {
                    width: "{#width}",
                    height: "{#height}",
                    display: "block"
                }
            },
            content: [
                {
                    name: 'svg',
                    tag: 'svg',
                    props: {
                        'class': 'f-svg',
                        style: {
                            width: "{#width}",
                            height: "{#height}"
                        }
                    }

                },

                {
                    name: 'zoomActionBar',
                    props: {
                        'class': 'f-topology-zoom-bar'
                    },
                    content: [
                        {
                            props: {
                                'class': 'f-topology-zoom-bar-pan'
                            },
                            content: [
                                {
                                    tag: 'i',
                                    props: {
                                        'class': 'icon-pan'
                                    }
                                },
                                {
                                    name: 'pan_up',
                                    tag: "a",
                                    props: {
                                        'class': 'f-topology-zoom-bar-pan_up'
                                    }
                                },
                                {
                                    name: 'pan_down',
                                    tag: "a",
                                    props: {
                                        'class': 'f-topology-zoom-bar-pan_down'
                                    }
                                },
                                {
                                    name: 'pan_left',
                                    tag: "a",
                                    props: {
                                        'class': 'f-topology-zoom-bar-pan_left'
                                    }
                                },
                                {
                                    name: 'pan_right',
                                    tag: "a",
                                    props: {
                                        'class': 'f-topology-zoom-bar-pan_right'
                                    }
                                },
                                {
                                    name: 'pan_center',
                                    tag: "a",
                                    props: {
                                        'class': 'f-topology-zoom-bar-pan_center'
                                    }
                                }
                            ]
                        },
                        {
                            name: 'zoomOut',
                            tag: "a",
                            props: {
                                'class': 'icon-zoomOut f-topology-zoom-bar-zoomOut'
                            }
                        },
                        {
                            name: 'zoomBar',
                            tag: "a",
                            props: {
                                'class': 'icon-zoomBar f-topology-zoom-bar-zoomBar'
                            }
                        },
                        {
                            name: 'zoomBall',
                            tag: "a",
                            props: {
                                'class': 'icon-zoomBall f-topology-zoom-bar-zoomBall'
                            }
                        },
                        {
                            name: 'zoomIn',
                            tag: "a",
                            props: {
                                'class': 'icon-zoomIn f-topology-zoom-bar-zoomIn'
                            }
                        },
                        {
                            name: 'fit',
                            tag: "a",
                            props: {
                                'class': 'icon-zoomAll f-topology-zoom-bar-zoomAll'
                            }
                        },
                        {

                            name: 'threeD',
                            tag: "a",
                            props: {
                                'class': 'icon-3d f-topology-zoom-bar-d',
                                visible: "{#!!groupBy}"
                            }
                        }
                    ]
                }
            ]
        },
        properties: {
            linkDistance: {
                value: 100
            },
            charge: {
                value: -1200
            },
            scale: {
                value: 1,
                observable: true
            },
            maxScale: {
                value: 3.4
            },
            selectableNodeNumber: {
                value: 1
            },
            autolayout: {
                value: true
            },
            revision: {
                getter: function () {
                    return this._revison;
                },
                setter: function (value) {
                    this._revison = value;
                    this.adjustLayout(value);
                }
            },
            groupBy: {
                getter: function () {
                    return this._groupBy || null;
                },
                setter: function (value) {
                    if (this._groupBy !== value) {
                        this._groupBy = value;
                        this._buildGroups();
                        this.notifyPropertyChanged("groupBy");
                    }
                }
            },
            nodeDraggable: {
                value: true
            },
            nodeLabelPath: {
                value: 'id',
                observable: true
            },
            iconTypePath: {
                value: 'type'
            },
            linkLabelPath: {
                value: null,
                observable: true
            },
            labelPositionOrder: {
                value: [2, 0, 1, 3]
            },
            linkDirection: {
                value: false
            },
            showInterface: {
                value: false,
                observable: true
            },
            interfacePath: {
                value: 'interface'
            },
            scalable: {
                getter: function () {
                    return !this._scalable == false;
                },
                setter: function (value) {
                    this._scalable = value;
                    this.__scalable();
                }
                //observable: true
            },
            padding: {
                value: 100
            },
            threshold: {
                value: 15000
            },
            transform: {
                getter: function () {
                    return this._transform;
                },
                setter: function (value) {
                    this._transform = value;
                    value == true && this._transformGroups();
                    // this.notifyPropertyChanged("transform");
                }
            },
            width: {
                getter: function () {
                    return this._width || null;
                },
                setter: function (value) {
                    this._width = value;
                    this.handler.attr("width", value);
                    this.notifyPropertyChanged("width");
                }
            },
            height: {
                getter: function () {
                    return this._height || null;
                },
                setter: function (value) {
                    this._height = value;
                    //  this.svg.attr("height", value);
                    this.handler.attr("height", value);
                    this.notifyPropertyChanged("height");
                }
            },
            visibleWidth: {
                readonly: true,
                getter: function () {
                    return this.width() - this.padding() * 2;
                }
            },
            visibleHeight: {
                readonly: true,
                getter: function () {
                    return this.height() - this.padding() * 2;
                }
            },
            highContrast: {
                value: false,
                observable: true
            },
            edgeRefType: {
                value: 'index'
            }
        },
        methods: {
            onInit: function () {
                this.inherited();
                //init scale
                this.xScale = new fruit.util.scale();
                this.yScale = new fruit.util.scale();
                this.__marker();
                this.__initHandler();
                this.__trigger();
            },
            __trigger: function () {
                var self = this;
                this.on("DBLCLICK", function (sender, event) {
                    // this.zoom(0.4);
                });
                this._element.addEventListener("contextmenu", function () {
                    self.zoom(-0.4);
                    self.adjustLayout();
                    return false;
                });
                this.$("pan_up").on("CLICK", function () {
                    this.move(0, -20);
                }, this);
                this.$("pan_down").on("CLICK", function () {
                    this.move(0, 20);
                }, this);
                this.$("pan_left").on("CLICK", function () {
                    this.move(-20, 0);
                }, this);
                this.$("pan_right").on("CLICK", function () {
                    this.move(20, 0);
                }, this);
                this.$("pan_center").on("CLICK", function () {
                    this.center();
                }, this);
                this.$("zoomOut").on("CLICK", function () {
                    this.zoom(0.3);
                    this.adjustLayout();
                }, this);
                this.$("zoomIn").on("CLICK", function () {
                    this.zoom(-0.3);
                    this.adjustLayout();
                }, this);

                this.$("zoomBall").on("MOUSEDOWN", function (sender, event) {
                    var self = this;
                    var ball = sender;
                    var _zoomBallTop = event.pageY - parseInt(sender.getStyle("top"));
                    var body = fruit.ui.Element.resolve(document.body);
                    var maxScale = 3.4;
                    var gapstep = (maxScale - 1) / 130;
                    var timer = 0;
                    body.on("MOUSEMOVE", function (sender, event) {
                        var top = event.pageY - _zoomBallTop;
                        top = Math.min(Math.max(102, top), 232);
                        ball.setStyles({
                            top: top
                        });
                        clearTimeout(timer);
                        timer = setTimeout(function () {
                            self.zoomTo(1 + (232 - top) * gapstep);
                        }, 300);
                    });
                    body.on("MOUSEUP", function (sender, event) {
                        body.off("MOUSEMOVE");
                        body.off("MOUSEUP")
                    })
                }, this);


                this.$("fit").on("CLICK", function () {
                    this.viewAll();
                }, this);

                this.$("threeD").on("CLICK", function () {
                    this.transform(true);
                }, this);

                this.listen("highContrast", function (sender, args) {
                    if (args.newValue) {
                        this.eachNode(function (node) {
                            node.invertImg(true);
                        })
                        this.eachLink(function (link) {
                            link.highLight(null, 2);
                        });
                    } else {
                        this.eachNode(function (node) {
                            node.invertImg(false);
                        })
                        this.eachLink(function (link) {
                            link.highLight();
                        });
                    }
                }, this);

            },
            __marker: function () {
                var defs = this.svg.append("svg:defs")

                defs.selectAll("marker")
                    .data(["link_edge"])
                    .enter().append("svg:marker")
                    .attr("id", String)
                    .attr("viewBox", "0 0 12 12")
                    .attr("refX", 35)
                    .attr("refY", -4.5)
                    .attr("overflow", "visible")
                    .attr("markerWidth", 7)
                    .attr("markerHeight", 7)
                    .attr("orient", "auto")
                    .append("svg:path")
                    .attr("d", "M0,-5L10,0L0,5");


                var linearGradient = defs.append("linearGradient")
                    .attr({
                        id: "hint_bg",
                        x1: "0%",
                        y1: "0%",
                        x2: "0%",
                        y2: "100%",
                        spreadMethod: "pad"
                    });
                linearGradient.append("stop")
                    .attr({
                        offset: "0%",
                        style: "stop-color:#faf0ae",
                        "stop-opacity": 1
                    });
                linearGradient.append("stop")
                    .attr({
                        offset: "100%",
                        style: "stop-color:#f3df74",
                        "stop-opacity": 1
                    });


                var invert = this.svg.append("filter").attr("id", "invert");
                var feColorMatrix = invert.append("feColorMatrix");
                feColorMatrix.attr({type: 'matrix', values: '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0'});


            },
            __initHandler: function () {
                var self = this;
                //var width = this.width();
                //var height = this.height();
                var handler = this.handler = this.svg.append("rect");
                this.svg.classed("f-topology-bg", true)
                    .attr("pointer-events", "all")
                    .on("mousedown", __mousedown)
                    .on("mousemove", __mousemove)
                    .on("mouseup", __mouseup)
                    .on("click", __click);
                var orginal_point;

                function __mousedown() {
                    orginal_point = d3.mouse(this);
                    self.fire("Updating", self);
                    self.fire("DragStart", self);
                }

                function __mousemove() {
                    if (orginal_point && self.scalable()) {
                        var current_point = d3.mouse(this);
                        self.move(current_point[0] - orginal_point[0], current_point[1] - orginal_point[1]);
                        orginal_point = current_point;
                        //
                        self.fire("Draging", self);
                        self.fire("Updating", self);
                    }
                }

                function __mouseup() {
                    orginal_point = null;
                    self.fire("Updating", self);
                    self.fire("DragEnd", self);
                }

                function __click() {
                    self.fire("ClickStage", self);
                    d3.event.stopPropagation();
                    d3.event.preventDefault();
                    return false;
                }

                //
                //this.__scalable();
            },
            __scalable: function () {
                var self = this;
                var zoomAction = function (sender, event) {
                    if (self.scalable()) {
                        var height = self.height();
                        var width = self.width();
                        var wheelDelta = event.detail * -1;
                        var step = fruit.browser.firefox ? 80 : 2000;
                        self.zoom(wheelDelta / step, width / 2 - event.originalEvent.offsetX, height / 2 - event.originalEvent.offsetY);
                        self.adjustLayout();
                        self.fire("Updating", self);
                    }
                    event.stopPropagation();
                    event.preventDefault();
                    return false;
                };


                fruit.ui.Element.resolve(this.svg.node()).on("MOUSEWHEEL", zoomAction);
                //this.handler.on("mousewheel", zoomAction);
                this.$("zoomActionBar").setStyle("display", this.scalable() ? "block" : "none");
            },
            __initGroups: function () {
                if (this.stage) {
                    this.stage.remove();
                }
                var stage = this.stage = this.svg.append("g").classed("stage", true).attr("svg_id", this.$id); // .attr("transform", "translate(" + padding + "," + padding + ")");
                this.groups = stage.append("g").attr("class", "groups").attr("pointer-events", "all");
                this.linksGroup = stage.append("g").attr("class", "links");
                this.nodesGroup = stage.append("g").attr("class", "nodes");
            },
            draw: function () {
                var self = this;
                var graph = this.graph;
                var $nodes = this.$nodes = {};
                var $links = this.$links = {};
                var $directionLinks = this.$directionLinks = {};
                this.selectedNode = [];
                this.selectedLink = [];
                this.__initGroups();
                //todo:
                var apNode;
                graph.eachVertex(function (vertex) {
                    var node = new fruit.graphic.TopologyNode(vertex, self);
                    var id = node.id = vertex.data.id == null ? node.$id : vertex.data.id;
                    node.on("Updating", function () {
                        self.redraw();
                        self.fire("Updating", self);
                    });
                    node.on("SelectNode", self.__SelectNode, self);
                    node.on("DragNodeStart", function (sender, args) {
                        this.fire('DragNodeStart', node);
                    }, self);
                    node.on("DragNode", function (sender, args) {
                        this.fire('DragNode', node);
                    }, self);
                    node.on("DragNodeEnd", function (sender, args) {
                        this.fire('DragNodeEnd', node);
                    }, self);
                    node.on("OverNode", function () {
                        this.fire("OverNode", node);
                    }, self);
                    node.on("OutNode", function () {
                        this.fire("OutNode", node);
                    }, self);
                    new fruit.Binding({
                        targetPath: "labelPath",
                        target: node,
                        sourcePath: "nodeLabelPath",
                        source: self
                    });
                    $nodes[id] = node;
                    self.nodesGroup.appendDOM(node.element);
                });
                graph.eachEdge(function (link) {
                    link.source = graph.getVertex(link.source);
                    link.target = graph.getVertex(link.target);
                    var _link = new fruit.graphic.TopologyLink(link, self);
                    _link.on("SelectLink", self.__SelectLink, self, d3.event);

                    _link.on("OverLink", function (sender, args) {
                        this.fire('OverLink', sender, args);
                    }, self);

                    _link.on("OutLink", function (sender, args) {
                        this.fire('OutLink', sender, args);
                    }, self);
                    _link.on("OverInterface", function () {
                        this.fire("OverInterface");
                    }, self);
                    _link.on("OutInterface", function () {
                        this.fire("OutInterface");
                    }, self);

                    var linkDirection = _link.getSourceNode().id + "_" + _link.getTargetNode().id;
                    var id = link.id = link.id || linkDirection;

                    //var id = link.id = link.id || _link.$id;
                    $links[id] = _link;
                    $directionLinks[linkDirection] = _link;
                    self.linksGroup.appendDOM(_link.element);


                    var reversLink = _link.getTargetNode().id + "_" + _link.getSourceNode().id;
                    if ($directionLinks[reversLink]) {
                        $directionLinks[reversLink].linkDirection(true);
                        _link.linkDirection(true);
                    }


                    //$directionLinks

                    new fruit.Binding({
                        target: _link,
                        targetPath: "showInterface",
                        sourcePath: "showInterface",
                        source: self
                    });
                    new fruit.Binding({
                        target: _link,
                        targetPath: "labelPath",
                        sourcePath: "linkLabelPath",
                        source: self
                    });

                });
            },
            redraw: function () {
                util.each(this.$nodes, function (node) {
                    node.redraw();
                });
                util.each(this.$links, function (link) {
                    link.redraw();
                });
                util.each(this.$groups, function (group) {
                    group.redraw();
                });
            },
            __SelectNode: function (sender, args) {
//                if (args.isSelected()) {
//                    util.each(this.selectedNode, function (node, index) {
//                        if (node == args) {
//                            node.unSelected();
//                            this.fire("UnSelectNode", node);
//                            this.selectedNode.splice(index, 1);
//                            return false;
//                        }
//                    }, this);
//                    return;
//                }
//                if (this.selectedNode.length == this.selectableNodeNumber()) {
//                    util.each(this.selectedNode, function (node) {
//                        node.unSelected();
//                        this.fire("UnSelectNode", node);
//                    }, this);
//                    this.selectedNode = [];
//                }
//                this.selectedNode.push(args);
//                args.selected(this.selectedNode.length);
                this.fire("SelectNode", args);
            },
            __SelectLink: function (sender, args) {
//                if (this.selectedLink.length == 1) {
//                    this.selectedLink.shift().unSelected();
//                }
//                this.selectedLink.push(args);
//                args.selected();
                this.fire("SelectLink", args);
            },
            getNode: function (id) {
                return this.$nodes[id];
            },
            getLink: function (id) {
                return this.$links[id];
            },
            eachNode: function (fn) {
                util.each(this.$nodes, function (node) {
                    fn.call(this, node);
                }, this)
            },
            eachLink: function (fn) {
                var links = this.$links;
                for (var i in links) {
                    var link = links[i];
                    fn.call(this, link);
                }
//                util.each(this.$links, function (link) {
//                    fn.call(this, link);
//                }, this)
            },
            eachSelectedNode: function (fn) {
                util.each(this.selectedNode, function (node) {
                    fn.call(this, node);
                }, this)
            },


            zoom: function (step, inOffsetX, inOffsetY, isRedraw) {
                var minScale = 1;
                var maxScale = this.maxScale();
                var scale = this.scale();
                var padding = this.padding();
                var width = this.width() - padding * 2;
                var height = this.height() - padding * 2;
                var currentScale = Math.max(Math.min(scale + step, maxScale), minScale);
                var min_dimension = Math.min(width, height);
                var zoomDistance = min_dimension * (currentScale - scale);


                //this.padding(this.padding.defaultValue - min_dimension * (currentScale - minScale) / 8);
                //this.__scaleOutput();


                //debugger;

                var offsetX = inOffsetX || 0;
                var offsety = inOffsetX || 0;


                var xOutput = this.xScale.output();
                var yOutput = this.yScale.output();
                if (xOutput && yOutput) {
                    this.xScale.output([xOutput[0] - zoomDistance, xOutput[1] + zoomDistance]);
                    this.yScale.output([yOutput[0] - zoomDistance, yOutput[1] + zoomDistance]);

                    this.fire("Zooming", this);
                    this.scale(currentScale);
                    if (isRedraw != false) {
                        this.redraw();
                    }
                    var gapstep = 130 / (maxScale - minScale);
                    this.$("zoomBall").setStyle("top", 232 - gapstep * (currentScale - minScale));
                }


            },
            zoomTo: function (value) {
                if (value && util.isNumber(value)) {
                    this.zoom(value - this.scale());
                    this.adjustLayout();
                }
            },
            move: function (x, y) {
                var xOutput = this.xScale.output();
                var yOutput = this.yScale.output();
                this.xScale.output([xOutput[0] + x || 0, xOutput[1] + x || 0]);
                this.yScale.output([yOutput[0] + y || 0, yOutput[1] + y || 0]);
                this.redraw();
            },

            center: function () {
                this.zoomTo(this.scale());


//                var box = this.nodesGroup.node().getBBox();
//                if (box.width == 0) {
//                    box = {width: this.width(), height: this.height(), x: 0, y: 0};
//                }
//                var x = (this.width() - box.width) / 2 - box.x;
//                var y = (this.height() - box.height) / 2 - box.y;
//                this.move(x, y);
            },
            viewAll: function () {
                //
                this.zoom(1 - this.scale(), 0, 0, false);
                //
                this.__scaleInput();
                this.__scaleOutput();
                //
                this.zoomTo(1);

                //this.center();
            },
            resize: function (width, height) {
                if (width) {
                    this.width(width);
                }
                if (height) {
                    this.height(height);
                }
                this.start();
            },
            fadeOut: function (force) {
                util.each(this.$nodes, function (node) {
                    node.fadeOut(force);
                });


                this.eachLink(function (link) {
                    setTimeout(function () {
                        link.fadeOut(force);
                    })
                });
            },
            fadeIn: function (force) {
                util.each(this.$nodes, function (node) {
                    node.fadeIn(force);
                });
                this.eachLink(function (link) {
                    link.recover(force);
                });
            },


            __scaleOutput: function () {
                var padding = this.padding();
                var width = this.width() - padding * 2;
                var height = this.height() - padding * 2;
                var dataBound = this.inputBound;

                if (dataBound.width == 0) {
                    this.xScale.input([0, dataBound.x * 2]);
                    this.xScale.output([padding, width + padding]);
                    this.yScale.output([padding, height + padding]);
                } else if (dataBound.height == 0) {
                    this.yScale.input([0, dataBound.y * 2]);
                    this.xScale.output([padding, width + padding]);
                    this.yScale.output([padding, height + padding]);
                } else if (width / height < dataBound.width / dataBound.height) { // width is scale
                    this.xScale.output([padding, width + padding]);
                    var rectHeight = this.xScale.get(dataBound.height + dataBound.x) - padding;
                    this.yScale.output([height / 2 - rectHeight / 2 + padding, height / 2 + rectHeight / 2 + padding]);
                } else {
                    this.yScale.output([padding, height + padding]);
                    var rectWidth = this.yScale.get(dataBound.width + dataBound.y) - padding;
                    this.xScale.output([width / 2 - rectWidth / 2 + padding, width / 2 + rectWidth / 2 + padding]);
                }
            },
            __scaleInput: function () {
                var graph = this.graph;
                var nodes = graph.getVertices();
                var edges = graph.getEdges();
                if (this.autolayout() || nodes[0].x == null) {
                    this.__force(nodes, edges);
                }
                var bound = this.inputBound = graph.getBound();
                this.xScale.input([bound.x, bound.width + bound.x]);
                this.yScale.input([bound.y, bound.height + bound.y]);
            },
            __force: function (vertices, edges) {
                var self = this;
                var force = d3.layout.force()
                    .size([self.visibleWidth(), self.visibleHeight()])
                    .charge(self.charge())
                    .linkDistance(self.linkDistance())
                force.nodes(vertices);
                force.links(edges);
                force.start();
                while (force.alpha()) {
                    force.tick();
                }
                force.stop();
            },

            adaptive: function (force) {
                var container = this.parent();
                if (force || (this.width() == null && container)) {
                    var bound = container.getBound();
                    this.width(bound.width || 800);
                    this.height(bound.height || 600);
                    if (bound.width == 0) {
                        console.log("Please set width and height to containor.");
                    }
                }
            },
            containerResize: function () {
                this.adaptive(true);
                this.viewAll();
            },
            adjustLayout: function () {
                var revision = this.revision();
                if (revision && this.stage) {
                    var box = this.nodesGroup.node().getBBox();
                    var nodes = this.graph._vertices.length;
                    var percell = (box.width * box.height) / nodes;
                    if (percell < this.threshold() / 2) {
                        this.eachNode(function (node) {
                            node.zoom(0.4);
                            //node.zoomInxx();
                        })
                    } else if (percell < this.threshold() / 1.5) {
                        this.eachNode(function (node) {
                            node.zoom(0.6);
                        })
                    } else if (percell < this.threshold()) {
                        this.eachNode(function (node) {
                            node.zoom(0.8);
                        })
                    } else {
                        this.eachNode(function (node) {
                            node.zoom(1);
                        })
                    }
                } else {
                    this.eachNode(function (node) {
                        node.zoom(1);
                    });
                }
            },
            setData: function (inData) {
                if (this.graph) {
                    this.zoomTo(1);
                }
                this.graph = new fruit.Graph(inData.nodes, inData.links, this.edgeRefType());
            },

            start: function () {
                var self = this;
                if (self.graph) {

                    this.adaptive();
                    this.__scaleInput();
                    this.__scaleOutput();

                    self.draw();
                    self.redraw();

                    self._buildGroups();
                    self.model() && self.model().set("graph", self.graph);

                    setTimeout(function () {
                        self.viewAll();
                        self.adjustLayout();
                    });
                }
                setTimeout(function () {

                });
            },
            //
            highlightNode: function (args) {
                var node, targetNode;
                var self = this;
                if (args instanceof fruit.graphic.TopologyNode) {
                    node = args;
                } else if (util.isFunction(args)) {
                    node = util.find(this.$nodes, function (node) {
                        return fn.call(this, node);
                    }, this);
                }
                //todo
                this.fadeOut();
                node.selected();
                node.enlarge();
                node.eachLink(function (link) {
                    targetNode = self.getNode(link.data.target.data.id);
                    if (targetNode == node) {
                        targetNode = self.getNode(link.data.source.data.id);
                    }
                    targetNode.selected();
                    setTimeout(function () {
                        link.highLight(null, 2);
                    });

                });
                return node;
            },
            highLightNodes: function (inArray, inKey, force) {
                var key = inKey || "id";
                var keyArray = [];
                var matchNodes = [];

                util.each(inArray, function (value) {
                    keyArray.push(value[key]);
                });
                this.fadeOut(force);
                this.eachNode(function (node) {
                    if (util.contains(keyArray, node.data[key])) {
                        node.fadeIn(force);
                        matchNodes.push(node);
                    }
                });
                return matchNodes;
            },
            highLightLinks: function (inArray, force) {
                var self = this;
                var matchLinks = [];
                util.each(inArray, function (linkData) {
                    var link = self.getLink(linkData.source + "_" + linkData.target) || self.getLink(linkData.target + "_" + linkData.source);
                    if (link) {
                        setTimeout(function () {
                            link.fadeIn(force);
                            link.highLight(null, 2, force);
                        });
                        matchLinks.push(link);
                    }
                });
                return matchLinks;
            },
            recover: function (force) {
                this.fadeIn(force);
//                this.eachLink(function (link) {
//                    link.recover(force);
//                });
            },


            _buildGroups: function () {
                var self = this;
                var key = this.groupBy();
                var $groups = this.$groups = {};
                var i = 0;
                var groups;
                if (this.graph && key) {
                    if (util.isFunction(key)) {
                        groups = util.groupBy(this.$nodes, function (d) {
                            return key.call(self, d.data);
                        });
                    } else {
                        groups = util.groupBy(this.$nodes, function (d) {
                            return d.data[key]
                        });
                    }
                    util.each(groups, function (items, key) {
                        var group = new fruit.graphic.TopologyGroup(items, key, i++, self);
                        $groups[key] = group;
                        self.groups.appendDOM(group.element);
                        group.redraw();
                    });
                }
            },
            _transformGroups: function () {

                //
                this.zoomTo(1);
                this.highContrast(false);

                var self = this;

                setTimeout(function () {

                    var stageBound = self.getBound();
                    var bound = self.groups.node().getBBox();
                    var door = new fruit.graphic.Topology3D();
                    var rate = 0.36;
                    var frameItems = [];
                    var groupsLength = util.size(self.$groups);
                    var _width = bound.width * rate;
                    var frameLeft = (stageBound.width - 200) / (groupsLength + 1);
                    var leftGap = frameLeft;
                    var frameTop = (stageBound.height - bound.height) / 2;
                    if (frameLeft < _width / 2) {
                        frameLeft = (stageBound.width - _width - 200) / ((groupsLength - 1) || 1);
                        leftGap = _width / 2;
                    }
                    util.each(self.$groups, function (group, index) {
                        frameItems.push({
                            label: index,
                            top: frameTop,
                            left: frameLeft * frameItems.length + leftGap + 100 - _width / 2,
                            width: bound.width,
                            height: bound.height,
                            bound: bound,
                            content: group,
                            linksGroup: self.linksGroup,
                            index: index
                        });
                    });
                    door.setBound(stageBound);
                    door.setItems(frameItems);
                    door.on("Close", function () {
                        self.transform(false);
                    });
                    door.appendTo();
                    setTimeout(function () {
                        door.transform();
                    }, 10);


                }, 100);


            }
        }
    });
})
    (fruit, fruit.util);